package org.apache.ibatis.plugin;

import org.apache.ibatis.reflection.ExceptionUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author Clinton Begin
 */
public class Plugin implements InvocationHandler {

    /**
     * 目标对象
     */
    private final Object target;
    /**
     * Interceptor对象
     */
    private final Interceptor interceptor;

    /**
     * 记录了＠Signature注解中的信息
     */
    private final Map<Class<?>, Set<Method>> signatureMap;

    private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    /**
     * 创建代理对象
     * 用于为目标对象生成代理。
     *
     * @param target
     * @param interceptor
     * @return
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        /*
         * 获取插件类 @Signature 注解内容，并生成相应的映射结构。形如下面：
         * {
         *     Executor.class : [query, update, commit],
         *     ParameterHandler.class : [getParameterObject, setParameters]
         * }
         */
        // 获取用户自定义Interceptor中＠Signature注解的信息
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        // 获取目标类型
        Class<?> type = target.getClass();
        // 获取目标类型实现的接口，正如前文所述，拦截器可以拦截的4类对象都实现了相应的接口，这也是能
        // 使用JDK动态代理的方式创建代理对象的基础
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
            // 使用JDK动态代理的方式创建代理对象
            return Proxy.newProxyInstance(
                    type.getClassLoader(),
                    interfaces,
                    // 这里使用的InvocationHandler对象就是Plugin对象
                    new Plugin(target, interceptor, signatureMap));
        }
        return target;
    }

    /**
     * Plugin 实现了 InvocationHandler 接口，因此它的 invoke 方法会拦截所有的方法调用。
     * invoke 方法会对所拦截的方法进行检测，以决定是否执行插件逻辑。
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            /*
             * 获取当前方法所在类或接口中，可被当前Interceptor拦截的方法
             * 获取被拦截方法列表，比如：
             *    signatureMap.get(Executor.class)，可能返回 [query, update, commit]
             */
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            // 如果当前调用的方法需要被拦截，则调用Interceptor.intercept()方法进行拦截处理
            if (methods != null && methods.contains(method)) {
                // 执行插件逻辑
                return interceptor.intercept(new Invocation(target, method, args));
            }
            // 如果当前调用的方法不能被拦截，则调用target对象的相应方法(被拦截的方法)
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }

    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        // issue #251
        if (interceptsAnnotation == null) {
            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
        for (Signature sig : sigs) {
            Set<Method> methods = signatureMap.get(sig.type());
            if (methods == null) {
                methods = new HashSet<Method>();
                signatureMap.put(sig.type(), methods);
            }
            try {
                Method method = sig.type().getMethod(sig.method(), sig.args());
                methods.add(method);
            } catch (NoSuchMethodException e) {
                throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
            }
        }
        return signatureMap;
    }

    /**
     * 动态代理对象要实现的接口
     *
     * @param type
     * @param signatureMap
     * @return
     */
    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        Set<Class<?>> interfaces = new HashSet<Class<?>>();
        while (type != null) {
            for (Class<?> c : type.getInterfaces()) {
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[interfaces.size()]);
    }

}
